import { $set } from '@feng3d/serialization';
import { BufferAttribute3D, CanvasTexture3D, Color, IcosahedronGeometry3D, Mesh3D, MeshBasicMaterial3D, MeshPhongMaterial3D, Node3D, PerspectiveCamera3D, PlaneGeometry3D, Scene3D, StatsComponent, WebGLRenderer3D } from 'feng3d-three';

let container: HTMLElement;
let stats: StatsComponent;

let camera: PerspectiveCamera3D;
let scene: Scene3D;
let renderer: WebGLRenderer3D;

let mouseX = 0;
let mouseY = 0;

let windowHalfX = window.innerWidth / 2;
let windowHalfY = window.innerHeight / 2;

init();
animate();

function init()
{
    container = document.getElementById('container');

    camera = new Node3D().addComponent('PerspectiveCamera3D', { fov: 20, aspect: window.innerWidth / window.innerHeight, near: 1, far: 10000 });
    camera.entity.position.z = 1800;

    scene = new Node3D().addComponent('Scene3D');
    scene.background = new Color(0xffffff);

    const light = new Node3D().addComponent('DirectionalLight3D', { color: { value: 0xffffff } });
    light.entity.position.set(0, 0, 1);
    scene.entity.addChild(light.entity);

    // shadow

    const canvas = document.createElement('canvas');
    canvas.width = 128;
    canvas.height = 128;

    const context = canvas.getContext('2d');
    const gradient = context.createRadialGradient(canvas.width / 2, canvas.height / 2, 0, canvas.width / 2, canvas.height / 2, canvas.width / 2);
    gradient.addColorStop(0.1, 'rgba(210,210,210,1)');
    gradient.addColorStop(1, 'rgba(255,255,255,1)');

    context.fillStyle = gradient;
    context.fillRect(0, 0, canvas.width, canvas.height);

    const shadowTexture = new CanvasTexture3D(canvas);

    const shadowMaterial = $set(new MeshBasicMaterial3D(), { map: shadowTexture });
    const shadowGeo = $set(new PlaneGeometry3D(), { width: 300, height: 300, widthSegments: 1, heightSegments: 1 });

    let shadowMesh: Mesh3D;

    shadowMesh = new Node3D().addComponent('Mesh3D', { geometry: shadowGeo, material: shadowMaterial });
    shadowMesh.entity.position.y = -250;
    shadowMesh.entity.rotation.x = -Math.PI / 2;
    scene.entity.addChild(shadowMesh.entity);

    shadowMesh = new Node3D().addComponent('Mesh3D', { geometry: shadowGeo, material: shadowMaterial });
    shadowMesh.entity.position.y = -250;
    shadowMesh.entity.position.x = -400;
    shadowMesh.entity.rotation.x = -Math.PI / 2;
    scene.entity.addChild(shadowMesh.entity);

    shadowMesh = new Node3D().addComponent('Mesh3D', { geometry: shadowGeo, material: shadowMaterial });
    shadowMesh.entity.position.y = -250;
    shadowMesh.entity.position.x = 400;
    shadowMesh.entity.rotation.x = -Math.PI / 2;
    scene.entity.addChild(shadowMesh.entity);

    const radius = 200;

    const geometry1 = $set(new IcosahedronGeometry3D(), { radius, detail: 1 });

    const count = geometry1.attributes.position.count;
    geometry1.setAttribute('color', new BufferAttribute3D(new Float32Array(count * 3), 3));

    const geometry2 = geometry1.clone();
    const geometry3 = geometry1.clone();

    const color = new Color();
    const positions1 = geometry1.attributes.position as BufferAttribute3D;
    const positions2 = geometry2.attributes.position as BufferAttribute3D;
    const positions3 = geometry3.attributes.position as BufferAttribute3D;
    const colors1 = geometry1.attributes.color as BufferAttribute3D;
    const colors2 = geometry2.attributes.color as BufferAttribute3D;
    const colors3 = geometry3.attributes.color as BufferAttribute3D;

    for (let i = 0; i < count; i++)
    {
        color.setHSL((positions1.getY(i) / radius + 1) / 2, 1.0, 0.5);
        colors1.setXYZ(i, color.r, color.g, color.b);

        color.setHSL(0, (positions2.getY(i) / radius + 1) / 2, 0.5);
        colors2.setXYZ(i, color.r, color.g, color.b);

        color.setRGB(1, 0.8 - (positions3.getY(i) / radius + 1) / 2, 0);
        colors3.setXYZ(i, color.r, color.g, color.b);
    }

    const material = $set(new MeshPhongMaterial3D(), {
        color: { value: 0xffffff },
        flatShading: true,
        vertexColors: true,
        shininess: 0
    });

    const wireframeMaterial = $set(new MeshBasicMaterial3D(), { color: { value: 0x000000 }, wireframe: true, transparent: true });

    let mesh = new Node3D().addComponent('Mesh3D', { geometry: geometry1, material });
    let wireframe = new Node3D().addComponent('Mesh3D', { geometry: geometry1, material: wireframeMaterial });
    mesh.entity.addChild(wireframe.entity);
    mesh.entity.position.x = -400;
    mesh.entity.rotation.x = -1.87;
    scene.entity.addChild(mesh.entity);

    mesh = new Node3D().addComponent('Mesh3D', { geometry: geometry2, material });
    wireframe = new Node3D().addComponent('Mesh3D', { geometry: geometry2, material: wireframeMaterial });
    mesh.entity.addChild(wireframe.entity);
    mesh.entity.position.x = 400;
    scene.entity.addChild(mesh.entity);

    mesh = new Node3D().addComponent('Mesh3D', { geometry: geometry3, material });
    wireframe = new Node3D().addComponent('Mesh3D', { geometry: geometry3, material: wireframeMaterial });
    mesh.entity.addChild(wireframe.entity);
    scene.entity.addChild(mesh.entity);

    renderer = scene.addComponent('WebGLRenderer3D', { parameters: { antialias: true } });
    renderer.setPixelRatio(window.devicePixelRatio);
    renderer.setSize(window.innerWidth, window.innerHeight);
    container.appendChild(renderer.domElement);

    stats = scene.addComponent('StatsComponent');
    container.appendChild(stats.dom);

    document.addEventListener('mousemove', onDocumentMouseMove);

    //

    window.addEventListener('resize', onWindowResize);
}

function onWindowResize()
{
    windowHalfX = window.innerWidth / 2;
    windowHalfY = window.innerHeight / 2;

    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();

    renderer.setSize(window.innerWidth, window.innerHeight);
}

function onDocumentMouseMove(event: MouseEvent)
{
    mouseX = (event.clientX - windowHalfX);
    mouseY = (event.clientY - windowHalfY);
}

//

function animate()
{
    requestAnimationFrame(animate);

    render();
    stats.update();
}

function render()
{
    camera.entity.position.x += (mouseX - camera.entity.position.x) * 0.05;
    camera.entity.position.y += (-mouseY - camera.entity.position.y) * 0.05;

    camera.entity.lookAt(scene.entity.position);

    renderer.render(scene, camera);
}
